﻿#include "Vkbrd.h"

#include <QSystemTrayIcon>
#include <QMessageBox>
#include <QCloseEvent>
#include <QDomDocument>
#include <QFile>
#include <assert.h>
#include "Sequence.h"
#include <QThread>
#include <QTimer>
#include <QScrollBar>
#include <QXmlStreamWriter>

#include "CalibrateWindow.h"

#include <Windows.h>

#include "SequenceWidget.h"

namespace  vkbrd
{
	const char * configFileName = "config.xml";

	Vkbrd::Vkbrd(QWidget *parent, Qt::WFlags flags)
		: QMainWindow(parent, flags),
		sequenceViewWindow(this),
		switchUserAction(tr("Switch user"), this),
		showWindowAction(tr("Show main window"), this),
		exitProgramAction(tr("Exit"), this),
		showSequenceWindowAction(tr("Show sequence window"), this)
	{
		Sequence::init();
		ui.setupUi(this);
		ifResendKeys = false;

		gloves[0].setId(0); gloves[1].setId(1);

		fingerBars << ui.progressBar_0 << ui.progressBar_1 << ui.progressBar_2 << 
			ui.progressBar_3 << ui.progressBar_4 << ui.progressBar_5 << ui.progressBar_6 << 
			ui.progressBar_7 << ui.progressBar_8 << ui.progressBar_9;
		messageLabels << ui.gloveLeftMessage << ui.gloveRightMessage;
		gloveActiveCheckBoxes << ui.checkBoxGlove1Active << ui.checkBoxGlove2Active;
		portNames << ui.leftGlovePortName << ui.rightGlovePortName;

		readConfig();

		setupTrayIcon();
		setupMenuOptions();
		connectSignals();

		bool succ = true;
		for(int i = 0; i < numGloves; ++i)
		{
			succ &= connect(&gloves[i], SIGNAL(rawDataReceived(int,gIntFingers)),
				this, SLOT(fingerRawDataUpdate(int,gIntFingers)));
			succ &= connect(&gloves[i], SIGNAL(fingerStateChanged(int, BoolFingers)),
				this, SLOT(fingerStateChanged(int,BoolFingers)));
			succ &= connect(&gloves[i], SIGNAL(fingerStateChanged(int, BoolFingers)),
				&sequenceViewWindow, SLOT(fingerStateChanged(int,BoolFingers)));
		}

		succ &= connect(this, SIGNAL(sequenceFired(Sequence,bool)), &sequenceViewWindow, SLOT(displaySequenceName(Sequence,bool)));

		QTimer::singleShot(5, this, SLOT(resendKeys()));
		assert(succ && "Signal connection problem");
	}

	Vkbrd::~Vkbrd()
	{

	}

	void Vkbrd::iconActivated( QSystemTrayIcon::ActivationReason reason )
	{
		switch (reason) {
	 case QSystemTrayIcon::Trigger:
		 this->show();
		 break;
	 case QSystemTrayIcon::DoubleClick:
		 /*
		 iconComboBox->setCurrentIndex((iconComboBox->currentIndex() + 1)
		 % iconComboBox->count());*/

		 break;
	 case QSystemTrayIcon::MiddleClick:
		 {

			 break;
		 }
	 default:
		 ;
		}
	}

	void Vkbrd::showMessage()
	{

	}

	void Vkbrd::messageClicked()
	{

	}

	void Vkbrd::closeEvent( QCloseEvent *event )
	{
		if (trayIcon->isVisible()) {
			QMessageBox::information(this, tr("Systray"),
				tr("The program will keep running in the "
				"system tray. To terminate the program, "
				"choose <b>Quit</b> in the context menu "
				"of the system tray entry."));
			hide();
			event->ignore();
		}
	}

	const QString vkbrdXmlElementName("vkbrd");
	const QString globalXmlElementName("global");
	const QString usersXmlElementName("users");

	void Vkbrd::saveConfig()
	{
		QFile file(configFileName);
		if (!file.open(QIODevice::WriteOnly))
		{///@todo Error message not working?
			errorMessageWindow(QString(tr("Error reading config file")), QString(tr("Unable to open config.xml file")));
			return;
		}
		
		QXmlStreamWriter* xmlWriter = new QXmlStreamWriter();
		xmlWriter->setAutoFormatting(true);

		xmlWriter->setDevice(&file);
		xmlWriter->writeStartDocument();
		xmlWriter->writeStartElement(vkbrdXmlElementName);


		//Write global settings
		xmlWriter->writeStartElement(globalXmlElementName);
		for(int i = 0; i < numGloves; ++i)
			gloves[i].serialize(xmlWriter);
		xmlWriter->writeEndElement();

		for (int i=0; i< ui.profiles->count(); ++i)
		{
			KeyProfile* keyProfile = (KeyProfile*) (ui.profiles->item(i)->data(Qt::UserRole).value<void*>());
			keyProfile->serialize(xmlWriter);
		}

		xmlWriter->writeEndElement();
		xmlWriter->writeEndDocument();
		delete xmlWriter;
	}

	void Vkbrd::readConfig()
	{
		QDomDocument doc("config");
		QFile file(configFileName);
		if (!file.open(QIODevice::ReadOnly))
		{///@todo Error message not working?
			errorMessageWindow(QString(tr("Error reading config file")), QString(tr("Unable to open config.xml file")));
			return;
		}

		if (!doc.setContent(&file)) {
			file.close();
			return;
		}

		QDomElement docElem = doc.documentElement();
		QDomElement root = doc.firstChildElement(vkbrdXmlElementName);

		{
			QDomElement global = root.firstChildElement(globalXmlElementName);
			QDomElement gloveElement = global.firstChildElement(DG5Glove::gloveXmlElementName);
				for (int i = 0;
					!gloveElement.isNull(); 
					gloveElement = gloveElement.nextSiblingElement(DG5Glove::gloveXmlElementName), ++i) 
				{
					if (i >= numGloves)
						break;
					gloves[i].deserialize(gloveElement);
				}
		}

		QDomElement profileElement = 
			root.firstChildElement(KeyProfile::keyProfileXmlElementName);

		for (int i = 0;
			!profileElement.isNull(); 
			profileElement = profileElement.nextSiblingElement(KeyProfile::keyProfileXmlElementName), ++i) 
		{
			KeyProfile* profile = new KeyProfile(profileElement);
			QListWidgetItem* profileWidget = new QListWidgetItem(profile->name);
			profileWidget->setData(Qt::UserRole, qVariantFromValue((void*)profile));
			ui.profiles->addItem(profileWidget);
		}

		//Update gui
		for (int i=0; i < numGloves; ++i)
			portNames[i]->setText(gloves[i].getPortName());
	}

	void Vkbrd::errorMessageWindow( QString& _text, QString& _informativeText )
	{
		QMessageBox msgBox;
		msgBox.setText(_text);
		msgBox.setInformativeText(_informativeText);
		msgBox.setStandardButtons(QMessageBox::Ok);
		msgBox.setDefaultButton(QMessageBox::Ok);
	}

	void Vkbrd::showWindow()
	{
		show();
	}

	void Vkbrd::exitProgram()
	{
		///@todo write config
		QApplication::quit();
	}

	void Vkbrd::showSequenceWindow()
	{
		sequenceViewWindow.show();
	}

	void Vkbrd::setupTrayIcon()
	{
		trayIconMenu = new QMenu(this);
		trayIconMenu->addAction(&showWindowAction);
		trayIconMenu->addAction(&showSequenceWindowAction);
		trayIconMenu->addSeparator();
		trayIconMenu->addAction(&switchUserAction);
		trayIconMenu->addSeparator();
		trayIconMenu->addAction(&exitProgramAction);		


		trayIcon = new QSystemTrayIcon(this);
		trayIcon->setIcon(QIcon(":/Vkbrd/res/icons/glove_ico.png")); 
		trayIcon->setToolTip(tr("Virtual Keyboard for VHand gloves"));
		trayIcon->setContextMenu(trayIconMenu);
		trayIcon->show();
	}

	void Vkbrd::connectSignals()
	{
		connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
			this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));

		//Actions signals
		connect(&showWindowAction, SIGNAL(triggered()), this, SLOT(showWindow()));
		connect(&exitProgramAction, SIGNAL(triggered()), this, SLOT(exitProgram()));
		connect(&showSequenceWindowAction, SIGNAL(triggered()), this, SLOT(showSequenceWindow()));
	}

	void Vkbrd::setupMenuOptions()
	{
		ui.menuOptions->addAction(&showSequenceWindowAction);
		ui.menuOptions->addAction(&switchUserAction);
		ui.menuOptions->addSeparator();
		ui.menuOptions->addAction(&exitProgramAction);
	}

	void Vkbrd::addNewSequence()
	{
		SequenceWidget* newSequence = new SequenceWidget(this);
		ui.sequencesLayout->addWidget(newSequence);
		sequenceWidgets.append(newSequence);
		ui.sequencesScrollArea->scroll(0, -100);

		bool succ = connect(newSequence, SIGNAL(sequenceChanged()), this, SLOT(profileSequenceChanged()));
		Q_ASSERT(succ);
	}	

	void Vkbrd::profileSequenceChanged()
	{
		KeyProfile* keyProfile = getCurrentProfile();
		keyProfile->clearSequences();
		
		for(int i = 0; i<sequenceWidgets.count(); ++i)
		{
			SequenceWidget* seqWidget = sequenceWidgets[i];
			Sequence seq = seqWidget->getSequence();
			if (seq.getSetFlagsCount() > 0) //discard zero sequences
				keyProfile->addSequence(seq);
		}
	}

	void Vkbrd::profileNameEdited(QString _newName)
	{
		KeyProfile* currProfile = getCurrentProfile();
		currProfile->name = _newName;
		ui.profiles->currentItem()->setText(_newName);
	}

	void Vkbrd::gloveActivatedDeactivated()
	{
		for(int i=0; i <numGloves; ++i)
		{
			DG5Glove& glove = gloves[i];
			if(gloveActiveCheckBoxes[i]->isChecked())
			{
				if (!glove.connected())
				{
					glove.setPortName(portNames[i]->text());
					
					if(!glove.openGloveComPort())
						QTimer::singleShot(0, gloveActiveCheckBoxes[i], SLOT(toggle()));
				}
			}
			else
			{
				glove.closeGloveComPort();
			}
		}
	}

	void Vkbrd::fingerStateChanged(int _gloveNum, BoolFingers _val)
	{
		int fingerIndex = fingerCount * _gloveNum;
		for (int i = 0; i < fingerCount; ++i, ++fingerIndex) //update seq.
			currentSequence.set(fingerIndex, _val(i));

		if(ui.profiles->currentRow() >= 0)
		{
			KeyProfile* activeProfile = getCurrentProfile();
			QList<Sequence> matchingSequences;
			activeProfile->findMatchingSequences(currentSequence, matchingSequences);

			for (int i = 0; i < firedSequences.count(); ++i)
			{
				if (!matchingSequences.contains(firedSequences[i]))
				{
					emit sequenceFired(firedSequences[i], false);
					sendInput(firedSequences[i], false);	
				}
			}

			for (int i = 0; i < matchingSequences.count(); ++i)
			{
				if (!firedSequences.contains(matchingSequences[i]))
				{
					emit sequenceFired(matchingSequences[i], true); ///@ todo write finish
					sendInput(matchingSequences[i], true);	
				}
			}	

			firedSequences.clear();
			firedSequences = matchingSequences;
		}
	}

	void Vkbrd::resendKeys()
	{
		for (int i =0; i < firedSequences.count(); ++i)
			sendInput(firedSequences[i], true);

		if (ifResendKeys)
			QTimer::singleShot(5, this, SLOT(resendKeys()));
	}

	void Vkbrd::fingerRawDataUpdate(int _gloveNum, gIntFingers _fingerData)
	{
		int fingerIndex = _gloveNum * fingerCount;
		for (int i = 0; i < fingerCount; ++i, ++fingerIndex)
		{
			QProgressBar* pb = fingerBars[fingerIndex];
			int dbgMax = pb->maximum();
			int dbgMin = pb->minimum();

			pb->setValue(_fingerData[i]);
		}
	}

	void Vkbrd::addProfile() 
	{
		KeyProfile* keyProfile = new KeyProfile(tr("NewProfile")); 		
		QListWidgetItem* profileWidget = new QListWidgetItem(keyProfile->name);
		profileWidget->setData(Qt::UserRole, qVariantFromValue((void*)keyProfile));
		ui.profiles->addItem(profileWidget);

		ui.profileName->setEnabled(true);
		ui.addSeqBtn->setEnabled(true);
	}

	void Vkbrd::selectProfile(QListWidgetItem* _selectedProfile)
	{
		QWidget* seqWidgetToDel;
		while (!sequenceWidgets.isEmpty())
			delete sequenceWidgets.takeLast();

		if (_selectedProfile != NULL)
		{
			KeyProfile* prof = getCurrentProfile();
			ui.profileName->setText(prof->name);

			//Fill sequences
			QList<Sequence>& sequences = prof->getSequences();
			for(int i = 0; i < sequences.count(); ++i)
			{
				SequenceWidget* newSequence = new SequenceWidget(this);
				newSequence->setSequence(sequences[i]);
				ui.sequencesLayout->addWidget(newSequence);
				sequenceWidgets.append(newSequence);
				bool succ = connect(newSequence, SIGNAL(sequenceChanged()), this, SLOT(profileSequenceChanged()));
			}
		}
		
		///@todo scroll down
	}

	void Vkbrd::deleteActiveProfile()
	{
		///@todo  disable delete when no profile selected
		int indexToDelete = ui.profiles->currentRow();
		if (indexToDelete < 0)
			return;
		
		delete getCurrentProfile();
		delete ui.profiles->takeItem(indexToDelete);

		if (ui.profiles->count() == 0) //deleted only one
			selectProfile(NULL);
		else if (ui.profiles->count() == indexToDelete) //deleted last
			selectProfile(ui.profiles->item(indexToDelete-1));
		else
			selectProfile(ui.profiles->item(indexToDelete));
	}

	void Vkbrd::sendInput( Sequence &matchingSequence, bool _keyDown )
	{
		//int keyUpDownFlag = (!_keyDown) ? 0 : KEYEVENTF_KEYUP;

#ifdef _WIN32		
		INPUT input; 

		if (_keyDown)
		{
			input.type = INPUT_KEYBOARD; 
			input.ki.wVk = matchingSequence.getAction(); 
			input.ki.wScan = 0; 
			input.ki.dwFlags = 0; 
			input.ki.time = 0; 
			input.ki.dwExtraInfo = 0; 
		}
		else
		{
			input.type = INPUT_KEYBOARD; 
			input.ki.wVk = matchingSequence.getAction(); 
			input.ki.wScan = 0; 
			input.ki.dwFlags = KEYEVENTF_KEYUP  ; 
			input.ki.time = 0; 
			input.ki.dwExtraInfo = 0; 
		}

		SendInput(1, &input, sizeof(INPUT));
#else
	#error Linux input to be implemented.
#endif // _WIN32
	}

	void Vkbrd::showCalibrateWindow()
	{
		CalibrateWindow* calibrateWindow = new CalibrateWindow(this);
		bool succ = connect(calibrateWindow, SIGNAL(calibrateSequence(BoolFingers)),
			this, SLOT(calibrateSequence(BoolFingers)));
		for (int i = 0; i < numGloves; ++i)
			succ &= connect(&gloves[i], SIGNAL(calibrationChanged(int,FingerCalibration)), 
				calibrateWindow, SLOT(calibrationChanged(int,FingerCalibration)));
		
		assert(succ);

		calibrateWindow->nextScreen();
		for (int i = 0; i < numGloves; ++i)
			calibrateWindow->calibrationChanged(i, gloves[i].getFingerCalibration());
		calibrateWindow->show();
	}

	void Vkbrd::calibrateSequence( BoolFingers _seq )
	{
		for (int i = 0; i < numGloves; ++i)
			if (gloves[i].connected())
			{
				for (int j=0; j < fingerCount; ++j)
				{
					if (_seq(i*fingerCount + j))
					{
						//Fire calibrate for each glove only once
						gloves[i].calibrate(_seq.shiftRightCopy(i * fingerCount));
						j = fingerCount; //skip inner fora
					}
				}
			}
	}

	void Vkbrd::resetCalibration()
	{
		for (int i =0; i < numGloves; ++i)
			gloves[i].resetCalibration();
	}

	void Vkbrd::toggleResendKeys( bool _val )
	{
		ifResendKeys = _val;
		if (ifResendKeys)
			QTimer::singleShot(15, this, SLOT(resendKeys()));
	}
} //namespace  vkbrd
